{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit VellemanInputs;

interface

uses Classes;

const
  DEFAULT_POLLING_INTERVAL = 100;
  DEFAULT_REPEAT_DELAY = 500;
  DEFAULT_REPEAT_INTERVAL = 200;
  DEFAULT_MIN_VALUE = 0;
  DEFAULT_MAX_VALUE = 255;

type
  TAHMVellemanInputType = (inUnknown, inAnalog1, inAnalog2, inDigital);

  TAHMVellemanInputAction = class(TCollectionItem)
  private
    FActive: Boolean;
    FActiveTimestamp: LongWord;
    FRepeating: Boolean;
    FRepeatTimestamp: LongWord;
    FInputType: TAHMVellemanInputType;
    FInputName: String;
    FShortAction: String;
    FLongAction: String;
    FRepeatAction: Boolean;
    FRepeatDelay: Word;
    FRepeatInterval: Word;
  protected
    function CheckLongPress(Timestamp: LongWord): Boolean;
    function CheckRepeatPress(Timestamp: LongWord): Boolean;
    procedure Update(Velleman: TObject; Active: Boolean); overload;
  public
    constructor Create(Collection: TCollection); override;
    procedure Assign(Source: TPersistent); override;
  published
    property InputType: TAHMVellemanInputType read FInputType write FInputType;
    property InputName: String read FInputName write FInputName;
    property ShortAction: String read FShortAction write FShortAction;
    property LongAction: String read FLongAction write FLongAction;
    property RepeatAction: Boolean read FRepeatAction write FRepeatAction;
    property RepeatDelay: Word read FRepeatDelay write FRepeatDelay;
    property RepeatInterval: Word read FRepeatInterval write FRepeatInterval;
  end;

  TAHMVellemanDigitalAction = class(TAHMVellemanInputAction)
  public
    constructor Create(Collection: TCollection); override;
  end;

  TAHMVellemanAnalogAction = class(TAHMVellemanInputAction)
  private
    FMinValue: Integer;
    FMaxValue: Integer;
  protected
    procedure Update(Velleman: TObject; Value: Integer); overload;
  public
    constructor Create(Collection: TCollection); override;
    procedure Assign(Source: TPersistent); override;
  published
    property MinValue: Integer read FMinValue write FMinValue;
    property MaxValue: Integer read FMaxValue write FMaxValue;
  end;

  TAHMVellemanInputActions = class(TCollection)
  protected
    function GetAction(Index: Integer): TAHMVellemanInputAction;
  public
    property Actions[Index: Integer]: TAHMVellemanInputAction read GetAction;
  end;

  TAHMVellemanDigitalActions = class(TAHMVellemanInputActions)
  public
    procedure PopulateDefaults;
  end;

  TAHMVellemanAnalogActions = class(TAHMVellemanInputActions);

  TAHMVellemanInputs = class(TPersistent)
  private
    FOwner: TObject; // TAHMVellemanManager
    FActionsEnabled: Boolean;
    FPollingInterval: Integer;
    FCurrentAnalog1: Integer;
    FCurrentAnalog2: Integer;
    FAnalog: TAHMVellemanAnalogActions;
    FDigital: TAHMVellemanDigitalActions;
  protected
  public
    constructor Create(Owner: TObject); // TAHMVellemanManager
    destructor Destroy; override;
    property ActionsEnabled: Boolean read FActionsEnabled write FActionsEnabled;
    property CurrentAnalogValue1: Integer read FCurrentAnalog1;
    property CurrentAnalogValue2: Integer read FCurrentAnalog2;
    procedure UpdateInputs(Analog1, Analog2, Digital: LongInt);
  published
    property PollingInterval: Integer read FPollingInterval write FPollingInterval;
    property AnalogInputs: TAHMVellemanAnalogActions read FAnalog;
    property DigitalInputs: TAHMVellemanDigitalActions read FDigital;
  end;

implementation

uses SysUtils, VellemanManager;

// TAHMVellemanInputAction

constructor TAHMVellemanInputAction.Create(Collection: TCollection);
begin
  inherited;

  // Set default values for new input action
  FActive := False;
  FRepeatDelay := DEFAULT_REPEAT_DELAY;
  FRepeatInterval := DEFAULT_REPEAT_INTERVAL;
end;

function TAHMVellemanInputAction.CheckLongPress(Timestamp: LongWord): Boolean;
begin
  Result := (Timestamp > (FActiveTimestamp + FRepeatDelay));
end;

function TAHMVellemanInputAction.CheckRepeatPress(Timestamp: LongWord): Boolean;
begin
  // Repeat after first delay and then each repeat interval
  Result := (not FRepeating and CheckLongPress(Timestamp)) or
            (FRepeating and (Timestamp > (FRepeatTimestamp + FRepeatInterval)));

  if Result then
  begin
    // Set repeating flag and record timestamp for further repeats
    FRepeating := True;
    FRepeatTimestamp := Timestamp;
  end;
end;

procedure TAHMVellemanInputAction.Update(Velleman: TObject; Active: Boolean);
var
  Timestamp: LongInt;
begin
  // Get current timestamp from Velleman manager
  Timestamp := TAHMVellemanManager(Velleman).Timestamp;

  // Check for various state transitions
  if Active and not FActive then
  begin
    // Becoming active - log start time and initialise state
    FActive := True;
    FActiveTimestamp := Timestamp;
    FRepeating := False;
  end
  else if Active and FActive then
  begin
    // Still active - should we repeat actions?
    if FRepeatAction and CheckRepeatPress(Timestamp) then
    begin
      // Fire appropriate short or long repeat action
      if FLongAction = '' then
        TAHMVellemanManager(Velleman).TriggerAction(FShortAction)
      else
        TAHMVellemanManager(Velleman).TriggerAction(FLongAction);
    end;
  end
  else if FActive and not Active then
  begin
    // Becoming inactive - may need to fire appropriate action if not repeating
    if not FRepeating then
      if CheckLongPress(Timestamp) and (FLongAction <> '') then
        TAHMVellemanManager(Velleman).TriggerAction(FLongAction)
      else
        TAHMVellemanManager(Velleman).TriggerAction(FShortAction);
    FActive := False;
  end;
end;

procedure TAHMVellemanInputAction.Assign(Source: TPersistent);
begin
  if Source is TPersistent then
  begin
    FInputType := TAHMVellemanInputAction(Source).InputType;
    FInputName := TAHMVellemanInputAction(Source).InputName;
    FShortAction := TAHMVellemanInputAction(Source).ShortAction;
    FLongAction := TAHMVellemanInputAction(Source).LongAction;
    FRepeatAction := TAHMVellemanInputAction(Source).RepeatAction;
    FRepeatDelay := TAHMVellemanInputAction(Source).RepeatDelay;
    FRepeatInterval := TAHMVellemanInputAction(Source).RepeatInterval;
  end;
end;

// TAHMVellemanDigitalAction

constructor TAHMVellemanDigitalAction.Create(Collection: TCollection);
begin
  inherited;

  FInputName := 'Digital Input ' + IntToStr(Index + 1);
  FInputType := inDigital;
end;

// TAHMVellemanAnalogAction

constructor TAHMVellemanAnalogAction.Create(Collection: TCollection);
begin
  inherited;

  // Set default values for new input analog action
  FMinValue := DEFAULT_MIN_VALUE;
  FMaxValue := DEFAULT_MAX_VALUE;
end;

procedure TAHMVellemanAnalogAction.Update(Velleman: TObject; Value: Integer);
begin
  // Is value within range for this action?
  Update(Velleman, (Value >= FMinValue) and (Value <= FMaxValue));
end;

procedure TAHMVellemanAnalogAction.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  if Source is TPersistent then
  begin
    FMinValue := TAHMVellemanAnalogAction(Source).MinValue;
    FMaxValue := TAHMVellemanAnalogAction(Source).MaxValue;
  end;
end;

// TAHMVellemanInputActions

function TAHMVellemanInputActions.GetAction(Index: Integer): TAHMVellemanInputAction;
begin
  Result := TAHMVellemanInputAction(Items[Index]);
end;

// TAHMVellemanDigitalActions

procedure TAHMVellemanDigitalActions.PopulateDefaults;
begin
  // Ensure we have our 5 digital input instances
  while Count < 5 do Add;
end;

// TAHMVellemanInputs

constructor TAHMVellemanInputs.Create(Owner: TObject);
begin
  inherited Create;

  // Set default values in case configuration is missing
  FPollingInterval := DEFAULT_POLLING_INTERVAL;

  // Store owner property (TAHMVellemanManager)
  FOwner := Owner;

  // Create action collections
  FAnalog := TAHMVellemanAnalogActions.Create(TAHMVellemanAnalogAction);
  FDigital := TAHMVellemanDigitalActions.Create(TAHMVellemanDigitalAction);
end;

destructor TAHMVellemanInputs.Destroy;
begin
  FAnalog.Free;
  FDigital.Free;

  inherited;
end;

procedure TAHMVellemanInputs.UpdateInputs(Analog1, Analog2, Digital: LongInt);
var
  i: Integer;
begin
  // Cache current analog readings for use in gui
  FCurrentAnalog1 := Analog1;
  FCurrentAnalog2 := Analog2;

  // Don't respond to actions if not enabled - may be configuring
  if not FActionsEnabled then Exit;

  // Update analog action states
  for i := 0 to Pred(FAnalog.Count) do
    case FAnalog.Actions[i].InputType of
      inAnalog1: TAHMVellemanAnalogAction(FAnalog.Actions[i]).Update(FOwner, Analog1);
      inAnalog2: TAHMVellemanAnalogAction(FAnalog.Actions[i]).Update(FOwner, Analog2);
    end;

  // Update digital action states
  FDigital.Actions[0].Update(FOwner, (Digital and 1 <> 0));
  FDigital.Actions[1].Update(FOwner, (Digital and 2 <> 0));
  FDigital.Actions[2].Update(FOwner, (Digital and 4 <> 0));
  FDigital.Actions[3].Update(FOwner, (Digital and 8 <> 0));
  FDigital.Actions[4].Update(FOwner, (Digital and 16 <> 0));
end;


end.
